﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using ESRI.ArcGIS.Client.Symbols;
using ESRI.ArcGIS.Client.Tasks;
using ESRI.ArcGIS.Client.Toolkit;

using Microsoft.Surface.Presentation.Controls;

namespace eGrid_App
{
    /// <summary>
    /// This class is the container which holds the ArcGIS map.
    /// It defines the contact events needed to scale and pan the map. 
    /// It reflects these changes back onto the contained map.    
    /// </summary>
    public partial class ArcGisMap : UserControl
    {
        private Draw MyDrawObject;
        private Symbol _activeSymbol = null;
        private ScatterLayer parentLayer = null;
        private GhostFrame linkedGhostFrame = null;

        public ScatterLayer ParentScatterLayer
        {
            get
            {
                return parentLayer;
            }
            set
            {
                parentLayer = value;
            }
        }

        # region Map Properties

        /// <summary>
        /// Width of the map
        /// </summary>
        public double MapWidth
        {
            get
            {
                return MyMap.Width;
            }

            set
            {
                MyMap.Width = value;
            }
        }

        /// <summary>
        /// Height of the map
        /// </summary>
        public double MapHeight
        {
            get
            {
                return MyMap.Height;
            }

            set
            {
                MyMap.Height = value;
            }
        }

        /// <summary>
        /// The initial extent of the map 
        /// </summary>
        private Envelope initialExtent;
        public Envelope InitialExtent
        {
            get
            {
                return initialExtent;
            }

            set
            {
                initialExtent = value;
            }
        }

        /// <summary>
        /// Wether the layers list should be visible on the map
        /// </summary>
        private bool showLayersList = false;
        public bool ShowLayersList
        {
            get
            {
                return showLayersList;
            }

            set
            {
                showLayersList = value;
                if (showLayersList)
                {
                    LayersListBorder.Visibility = Visibility.Visible;
                    LayersList.Visibility = Visibility.Visible;
                }
                else
                {
                    LayersListBorder.Visibility = Visibility.Hidden;
                    LayersList.Visibility = Visibility.Hidden;
                }
            }
        }

        /// <summary>
        /// Returns the center of the extent of the map
        /// </summary>
        public Point Center
        {
            get
            {
                MapPoint centerPoint = MyMap.Extent.GetCenter();
                return MyMap.MapToScreen(centerPoint);
            }
        }

        /// <summary>
        /// Returns the actual Map object 
        /// contained in this class.
        /// </summary>
        /// <returns>The actual Map object represented by this class</returns>
        public Map Map
        {
            get
            {
                return MyMap;
            }
            set
            {
                MyMap = value;
            }
        }

        /// <summary>
        /// For Testing: Return the URL of the Map
        /// </summary>
        public string MapURL
        {
            get
            {
                ArcGISTiledMapServiceLayer arcgisLayer = MyMap.Layers["StreetMap"] as ArcGISTiledMapServiceLayer;
                return arcgisLayer.Url;
            }
        }

        /// <summary>
        /// For Testing: Return the extent of the map
        /// </summary>
        public Envelope Extent
        {
            get
            {
                return MyMap.Extent;
            }
        }

        /// <summary>
        /// For Testing: Return the zoom factor of the map
        /// </summary>
        public double ZoomFactor
        {
            get
            {
                return MyMap.ZoomFactor;
            }
        }

        private bool canPan = true;
        public bool CanPan
        {
            get
            {
                return canPan;
            }

            set
            {
                canPan = value;
            }
        }

        private bool canZoom = true;
        public bool CanZoom
        {
            get
            {
                return canZoom;
            }

            set
            {
                canZoom = value;
            }
        }
        
        # endregion

        # region Constructor and Initializations
        
        /// <summary>
        /// Default constructor
        /// </summary>
        public ArcGisMap()
        {
            InitializeComponent();

            initialExtent = MyMap.Extent;

            InitializeMapLayers();

            //MyMap.GotTouchCapture += new EventHandler<TouchEventArgs>(MyMap_GotTouchCapture);
            //MyMap.LostTouchCapture += new EventHandler<TouchEventArgs>(MyMap_LostTouchCapture);

            initializeGraphics();
        }

        void MyMap_LostTouchCapture(object sender, TouchEventArgs e)
        {
            throw new NotImplementedException();
        }

        private void InitializeMapLayers()
        {
            LayerCollection layers = MapLayersManager.Instance.MapLayers;

            foreach (Layer layer in layers)
            {
                Layer clone = MapLayersManager.Instance.CloneLayer(layer);
                if (clone != null)
                    AddMapLayer(clone);
            }
        }

        private void InitializeDefaultMapLayers()
        {
            ArcGISTiledMapServiceLayer streetLayer = new ArcGISTiledMapServiceLayer();

            streetLayer.Initialized += (evtsender, args) =>
                {
                    MyMap.ZoomTo(initialExtent);
                };

            streetLayer.ID = "StreetMap";
            streetLayer.Opacity = 1;
            streetLayer.Url = "http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer";

            ArcGISTiledMapServiceLayer imageryLayer = new ArcGISTiledMapServiceLayer();

            imageryLayer.Initialized += (evtsender, args) =>
            {
                MyMap.ZoomTo(initialExtent);
            };

            imageryLayer.ID = "Imagery";
            imageryLayer.Opacity = 1;
            imageryLayer.Visible = false;
            imageryLayer.Url = "http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer";

            ArcGISTiledMapServiceLayer topoLayer = new ArcGISTiledMapServiceLayer();

            topoLayer.Initialized += (evtsender, args) =>
            {
                MyMap.ZoomTo(initialExtent);
            };

            topoLayer.ID = "ShadedRelief";
            topoLayer.Opacity = 1;
            topoLayer.Visible = false;
            topoLayer.Url = "http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_ShadedRelief_World_2D/MapServer";
            
            MyMap.Layers.Add(streetLayer);
            MyMap.Layers.Add(imageryLayer);
            MyMap.Layers.Add(topoLayer);
        }

        /// <summary>
        /// This method is to be used only when this object is used as the background map in a scatter layer
        /// </summary>
        /// <param name="sl"></param>
        public void setParentScatterLayer(ScatterLayer sl)
        {
            parentLayer = sl;
        }

        /// <summary>
        /// This method is used to link the map with its ghost frame
        /// </summary>
        /// <param name="gf"></param>
        public void setLinkedGhostFrame(GhostFrame gf)
        {
            linkedGhostFrame = gf;
        }

        #endregion

        # region Map Event Handlers

        /// <summary>
        /// Scaling the map
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {

            if (e.PreviousSize.Height == 0)
                return;

            // Resize the map grid
            MapWidth = e.NewSize.Width;
            MapHeight = e.NewSize.Height;

            ReloadMap();
        }

        private void MyMap_Initialized(object sender, EventArgs e)
        {
            MyMap.ZoomTo(initialExtent);
            if (linkedGhostFrame != null)
            {
                linkedGhostFrame.UpdatePosition(Extent);
            }
        }

        private void MyMap_ExtentChanged(object sender, ExtentEventArgs e)
        {
            if (e.OldExtent == null)
            {
                initialExtent = e.NewExtent;
            }
            if (linkedGhostFrame != null)
            {
                linkedGhostFrame.UpdatePosition(Extent);
            }
        }
        
        public void MyMap_ExtentChanging(object sender, ESRI.ArcGIS.Client.ExtentEventArgs e)
        {
            if (!canPan || !canZoom)
            {
                MyMap.Extent = initialExtent;                
            }
            if (linkedGhostFrame != null)
            {
                linkedGhostFrame.UpdatePosition(Extent);
            }
        }

        private void MyMap_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (!canPan || !canZoom)
                e.Handled = true;
        }

        public void MyMap_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!canPan || !canZoom)
            {
                MyMap.ExtentChanging += new EventHandler<ESRI.ArcGIS.Client.ExtentEventArgs>(MyMap_ExtentChanging);
                initialExtent = MyMap.Extent;
                e.Handled = true;
            }            
        }

        public void MyMap_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!canPan || !canZoom)
            {
                MyMap.ExtentChanging -= MyMap_ExtentChanging;
                e.Handled = true;
            }
        }

        public void MyMap_TouchDown(object sender, TouchEventArgs e)
        {
            Map captured = sender as Map;
            /* once we received a touchdown event, enable manipulation mode
             * on the target Map so we can zoom/pan it */
            captured.IsManipulationEnabled = true;

            if (!canPan || !canZoom)
            {
                //MyMap.ExtentChanging += new EventHandler<ESRI.ArcGIS.Client.ExtentEventArgs>(MyMap_ExtentChanging);
                //initialExtent = MyMap.Extent;

                //e.TouchDevice.Capture(this);

                //e.Handled = true;
            }
            else
            {
                e.TouchDevice.Capture(captured);
            }
        }

        public void MyMap_TouchUp(object sender, TouchEventArgs e)
        {
            Map captured = sender as Map;
            /* once we received a touchup event, diable 
             * manipulation mode such that the user can
             * no longer pan/zoom the map */
            captured.IsManipulationEnabled = false;
            if (e.TouchDevice.Captured == captured)
            {
                captured.ReleaseAllTouchCaptures();
                captured.ReleaseMouseCapture();
            }
        }
        

        # endregion

        # region ZoomingAndPanning
        
        /// <summary>
        /// Zoom in or out
        /// </summary>
        /// <param name="factor"></param>
        public void Zoom(double factor)
        {
            // > 1 = zoom in
            // < 1 = zoom out
            MyMap.Zoom(factor);
        }

        /// <summary>
        /// Zoom to a specific extent...
        /// Map animation will operate during the extent change. 
        /// </summary>
        /// <param name="geom"></param>
        public void ZoomTo(ESRI.ArcGIS.Client.Geometry.Geometry geom)
        {
            MyMap.ZoomTo(geom);
        }


        /// <summary>
        /// Pan to a screen point
        /// PanTo: Pan to an extent that centers on the ESRI.ArcGIS.Client.Geometry instance passed to the method. 
        /// Map scale will not change. Map animation will operate during the operation.
        /// </summary>
        /// <param name="screenPoint"></param>
        public void PanTo(System.Windows.Point screenPoint)
        {
            if (MyMap.Extent != null)
            {
                MapPoint mapPoint = MyMap.ScreenToMap(screenPoint);                                
                MyMap.PanTo(mapPoint);
            }

        }

        /// <summary>
        /// Set the extent from latitude and longitude.
        /// X values are longitudes and Y values are latitudes
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        public void setExtentFromLtdLng(double lat, double lng)
        {            
            double x1, y1, x2, y2;

            x1 = lng + 0.01;
            x2 = lng - 0.01;

            y1 = lat + 0.001;
            y2 = lat - 0.001;

            Envelope newExtent = new Envelope(x1, y1, x2, y2);
            MyMap.Extent = newExtent;            
        }

        /// <summary>
        /// Set the extent of the the map using an
        /// Evelope arugment such that the map will
        /// display the given area.
        /// </summary>
        /// <param name="input">The Envelope area to be shown</param>
        public void setExtentFromEnvelop(Envelope input)
        {
            if (MyMap.Extent == null)
            {
                Envelope env = new Envelope(input.XMax, input.YMax, input.XMin, input.YMin);
                MyMap.Extent = env;
            }
            else
            {
                MyMap.Extent = input;
            }

        }

        # endregion ZoomingAndPanning

        # region Annotations

        ///// <summary>
        ///// Used the change the visibility of the annotations toolbar
        ///// </summary>
        //public void ToggleAnnotationsToolbarVisibility()
        //{
        //    if (AnnotationsToolbar.Visibility == Visibility.Hidden)
        //        AnnotationsToolbar.Visibility = Visibility.Visible;
        //    else
        //        AnnotationsToolbar.Visibility = Visibility.Hidden;
        //}

        /// <summary>
        /// Initialize the graphics layer and draw object
        /// </summary>
        private void initializeGraphics()
        {
            MyDrawObject = new Draw(MyMap)
            {
                LineSymbol = LayoutRoot.Resources["DrawLineSymbol"] as LineSymbol,
                FillSymbol = LayoutRoot.Resources["DrawFillSymbol"] as FillSymbol
            };

            MyDrawObject.DrawComplete += MyDrawObject_DrawComplete;
            MyDrawObject.DrawMode = DrawMode.None;
            MyDrawObject.IsEnabled = false;
            
        }

        /// <summary>
        /// Event handler for Draw Complete event of the draw object
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void MyDrawObject_DrawComplete(object sender, ESRI.ArcGIS.Client.DrawEventArgs args)
        {
            GraphicsLayer graphicsLayer = MyMap.Layers["AnnotationsLayer"] as GraphicsLayer;
            ESRI.ArcGIS.Client.Graphic graphic = new ESRI.ArcGIS.Client.Graphic()
            {
                Geometry = args.Geometry,
                Symbol = _activeSymbol,
            };
            graphicsLayer.Graphics.Add(graphic);            
        }

        /// <summary>
        /// Returns whether the map is in the draw mode
        /// </summary>
        /// <returns>Whether the draw mode is enabled or not</returns>
        public bool isMapInDrawMode()
        {
            if (MyDrawObject.DrawMode != DrawMode.None)
            {
                return true;
            }
            return false;
        }

        // Returns whether the layers list is visible
        public bool isLayersListVisible()
        {
            if (showLayersList) return true;

            return false;
        }

        /// <summary>
        /// Used to set the draw mode to point
        /// </summary>
        public void SetDrawPointMode()
        {
            MyDrawObject.DrawMode = DrawMode.Point;
            _activeSymbol = LayoutRoot.Resources["DefaultMarkerSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to polyline
        /// </summary>
        public void SetDrawPolylineMode()
        {
            MyDrawObject.DrawMode = DrawMode.Polyline;
            _activeSymbol = LayoutRoot.Resources["DefaultLineSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to polygon
        /// </summary>
        public void SetDrawPolygonMode()
        {
            MyDrawObject.DrawMode = DrawMode.Polygon;
            _activeSymbol = LayoutRoot.Resources["DefaultFillSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to rectangle
        /// </summary>
        public void SetDrawRectangleMode()
        {
            MyDrawObject.DrawMode = DrawMode.Rectangle;
            _activeSymbol = LayoutRoot.Resources["DefaultFillSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Used to set the draw mode to free hand
        /// </summary>
        public void SetDrawFreehandMode()
        {
            MyDrawObject.DrawMode = DrawMode.Freehand;
            _activeSymbol = LayoutRoot.Resources["DefaultLineSymbol"] as Symbol;
            MyDrawObject.IsEnabled = true;
            MyMap.IsManipulationEnabled = false;
        }

        /// <summary>
        /// Clears the graphics that are drawn on the map
        /// </summary>
        public void ClearGraphics()
        {
            StopDrawing();
            GraphicsLayer graphicsLayer = MyMap.Layers["AnnotationsLayer"] as GraphicsLayer;
            graphicsLayer.ClearGraphics();            
        }

        /// <summary>
        /// disabled the drawing mode
        /// </summary>
        public void StopDrawing()
        {
            MyDrawObject.DrawMode = DrawMode.None;
            MyDrawObject.IsEnabled = false;
            MyMap.IsManipulationEnabled = true;
        }        

        # endregion

        # region OtherMembers

        /// <summary>
        /// Refresh the map
        /// </summary>
        public void ReloadMap()
        {
            MyMap.UpdateLayout();
        }

        /// <summary>
        /// gets image of the current view
        /// </summary>
        /// <returns></returns>
        public ImageSource ToImageSource()
        {
            FrameworkElement obj = this;

            // Save current canvas transform
            Transform transform = obj.LayoutTransform;
            obj.LayoutTransform = null;

            // fix margin offset as well
            Thickness margin = obj.Margin;
            obj.Margin = new Thickness(0, 0,
            margin.Right - margin.Left, margin.Bottom - margin.Top);

            // Get the size of canvas
            System.Windows.Size size = new System.Windows.Size(obj.ActualWidth, obj.ActualHeight);

            // force control to Update
            obj.Measure(size);
            obj.Arrange(new Rect(size));

            RenderTargetBitmap bmp = new RenderTargetBitmap(
            (int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);

            bmp.Render(obj);

            // return values as they were before
            obj.LayoutTransform = transform;
            obj.Margin = margin;

            return bmp;
        }


        # endregion OtherMembers

        #region Vehicles Layer

        public void AddVehiclePin(VehiclePin pin)
        {
            GraphicsLayer gLayer = MyMap.Layers["VehiclesLayer"] as GraphicsLayer;

            Graphic vehicle = new Graphic() 
            {
                Symbol = LayoutRoot.Resources["VehicleSymbol"] as PictureMarkerSymbol,
                Geometry = pin.Location,
            };

            gLayer.Graphics.Add(vehicle);

        }
        #endregion

        #region Trouble Tickets Layer

        public void AddTroubleTicketPin(BookmarkPin pin)
        {
            GraphicsLayer graphicsLayer = MyMap.Layers["TroubleTicketsLayer"] as GraphicsLayer;

            BookmarkPinGraphic graphic = new BookmarkPinGraphic();

            graphic.Geometry = new MapPoint(pin.Bookmark.MapCoordinate.X, pin.Bookmark.MapCoordinate.Y);
            if (pin.Priority == Bookmark.BookmarkPriority.High)
            {
                graphic.Symbol = LayoutRoot.Resources["HighPriorityPinSymbol"] as SimpleMarkerSymbol;
            }
            else if (pin.Priority == Bookmark.BookmarkPriority.Medium)
            {
                graphic.Symbol = LayoutRoot.Resources["MeduimPriorityPinSymbol"] as SimpleMarkerSymbol; 
            }
            else if (pin.Priority == Bookmark.BookmarkPriority.Low)
            {
                graphic.Symbol = LayoutRoot.Resources["LowPriorityPinSymbol"] as SimpleMarkerSymbol; 
            }

            /* Which mouse button event to listen for? */
            //graphic.MouseRightButtonDown += new MouseButtonEventHandler(graphic_MouseRightButtonDown);
            graphic.MouseLeftButtonDown += new MouseButtonEventHandler(graphic_MouseLeftButtonDown);

            BookmarkInfoTip mapTip = null;
            if (parentLayer != null)
            {
                mapTip = new BookmarkInfoTip(parentLayer, pin);
                /* prepare the MapTip in the background map but don't make it visible */
                mapTip.Visibility = Visibility.Collapsed;
                parentLayer.AddMapTip(mapTip, pin.ScreenPt, 0.0);                
            }
            else
            {
                mapTip = new BookmarkInfoTip(pin);
            }
            
            
            graphic.MapTip = mapTip;

            graphicsLayer.Graphics.Add(graphic);            
        }

        void graphic_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var clickedPin = sender as BookmarkPinGraphic;
            var sel = clickedPin.Selected;

            var mapPoint = clickedPin.Geometry as MapPoint;
            Point scrPoint = Map.MapToScreen(mapPoint);

            var mapTip = clickedPin.MapTip as ScatterViewItem;
            mapTip.Center = scrPoint;

            mapTip.Visibility = Visibility.Visible;

            clickedPin.UnSelect();
        }

        void graphic_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {

        }

        /// <summary>
        /// Used for demo purpose.  Populates the background map
        /// with TroubleTicketPins representing the location of 
        /// each TroubleTickets.
        /// </summary>
        public void CreateTroublePins(List<Bookmark> troubleTickets)
        {
            foreach (Bookmark tt in troubleTickets)
            {
                // TODO: Check if we need screen points vs map points
                //Point pt = Map.MapToScreen(tt.MapCoordinate);
                BookmarkPin ttpin = new BookmarkPin(tt, new Point(0, 0));
                AddTroubleTicketPin(ttpin);
            }
        }

        
        #endregion

        #region Map Layers Management

        public void AddMapLayer(Layer addLayer)
        {
            MyMap.Layers.Add(addLayer);
        }

        public bool RemoveMapLayer(Layer deleteLayer)
        {
            LayerCollection layers = MyMap.Layers;

            foreach (Layer layer in layers)
            {
                if (layer.ID == deleteLayer.ID)
                    return MyMap.Layers.Remove(layer);
            }
            return false;
        }

        #endregion
                
    }
}
